package FlexTrade.Auctions;

//import Customer;

import java.io.Serializable;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.Date;
import java.util.LinkedList;
import java.util.Stack;
import java.util.Timer;
import java.util.TimerTask;

import net.jini.core.event.EventRegistration;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.RemoteEventListener;
import FlexTrade.AuctionPrimitiveData;
import FlexTrade.AuctionStates;
import FlexTrade.CustomerData;
import FlexTrade.IAuction;
import FlexTrade.IRemoteAccounts;
import FlexTrade.Product;
import FlexTrade.ProviderData;
import FlexTrade.BidPriceData;
import net.jini.core.event.UnknownEventException ;
import net.jini.core.lease.Lease;
import net.jini.core.lookup.ServiceID;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.core.transaction.CannotAbortException;
import net.jini.core.transaction.CannotJoinException;
import net.jini.core.transaction.UnknownTransactionException;
import net.jini.core.transaction.server.CrashCountException;
import net.jini.core.transaction.server.TransactionConstants;
import net.jini.core.transaction.server.TransactionManager;
import net.jini.core.transaction.server.TransactionParticipant;
import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.export.Exporter;
import net.jini.export.ProxyAccessor;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.tcp.TcpServerEndpoint;
import net.jini.lease.LeaseRenewalManager;
import net.jini.lookup.ServiceDiscoveryManager;
import javax.swing.event.EventListenerList;


public class DutchAuctionImpl implements IAuction, TransactionParticipant, ProxyAccessor {

	private static final long serialVersionUID = 1L;
	private static final long WAITFOR = 100000L;

	//Information about last bid price received from customer
	private BidPriceData lastBidPriceData;
	//Information about last customer joining auction
	private CustomerData lastCustomerData;
	//Detail information about this auction
	private AuctionPrimitiveData auctionPrimitiveData; 
	//List of all bid price from customers
	private LinkedList<BidPriceData> listOfParticipants;
	//Current status of this auction
	private AuctionStates auctionStates;
	//list of registered listener to this class
	private EventListenerList listenerList = new EventListenerList();
	//remote proxy of this class
	private Remote proxy;
	private long seqNum = 0L;
	//remote bank serivce to transfer payment between customer and provider
	IRemoteAccounts rma = null;
	long crashCount = 0;
	public String uniqueAuctionID;	//unique auction ID of the object


	public DutchAuctionImpl(String _uniqueAuctionID) {
		this.uniqueAuctionID = _uniqueAuctionID;
		listOfParticipants = new LinkedList<BidPriceData>();
		auctionStates = new AuctionStates();
	}
	
	public DutchAuctionImpl(){
		listOfParticipants = new LinkedList<BidPriceData>();
		auctionStates = new AuctionStates();
	}
		
	public DutchAuctionImpl(AuctionPrimitiveData _auctionPrimitiveData) {
		// TODO Auto-generated constructor stub
		listOfParticipants = new LinkedList<BidPriceData>();
		auctionStates = new AuctionStates();
		auctionStates.acceptableBidPrice = _auctionPrimitiveData.acceptablePrice;
		this.auctionPrimitiveData = _auctionPrimitiveData;
		this.uniqueAuctionID = _auctionPrimitiveData.uniqueAuctionID;
	}
	

	@Override
	public boolean receiveBidPriceFromCustomer(BidPriceData _bidPriceData) 
														throws RemoteException {
		//this is Dutch auction ==> do nothing
		return false;
	}

	@Override
	public EventRegistration addRemoteListener(RemoteEventListener listener) 
												throws RemoteException {
		// TODO Auto-generated method stub
		listenerList.add(RemoteEventListener.class, listener);
		
		//notify provider and existing customers
		fireNotify(IAuction.D_CUSTOMER_JOIN);
		
		return new EventRegistration(0,
		proxy,
		null, /* Lease is null for simplicity only.
		It should be e.g. a LandlordLease
		*/
		0);
	}

	@Override
	public boolean receiveNewAcceptablePriceFromProvider(float newAcceptablePrice)
																throws RemoteException {
		// TODO Auto-generated method stub
		if(newAcceptablePrice <= this.auctionStates.acceptableBidPrice) {	//if new offer is lower then
			//update auction status
			this.auctionStates.acceptableBidPrice = newAcceptablePrice;
		}
		fireNotify(IAuction.D_ACCEPTABLE_PRICE_RECEIVED);
		return true;
	}

	@Override
	public boolean receivePriceAcceptanceFromCustomer(BidPriceData _bidPriceData)
			throws RemoteException {
		// TODO Auto-generated method stub
		this.auctionStates.acceptableBidPrice = _bidPriceData.bidPrice;
		this.auctionStates.highestBidCustomer = _bidPriceData.customer;
		this.auctionStates.currentStatus = IAuction.AUCTION_FINISHED;
		this.lastBidPriceData = _bidPriceData;
		fireNotify(IAuction.D_CUSTOMER_ACCEPT);
		return true;
	}

	public Object getProxy() {
		return proxy;
	}
	
	protected void fireNotify(long eventID) {
		RemoteEvent remoteEvent = null;
		// Guaranteed to return a non-null array
		Object[] listeners = listenerList.getListenerList();
		// Process the listeners last to first, notifying
		// those that are interested in this event
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == RemoteEventListener.class) {
				RemoteEventListener listener = (RemoteEventListener) listeners[i+1];
				if (remoteEvent == null) {
					remoteEvent = new RemoteEvent(proxy, eventID, seqNum++, null);
				}
				try {
					listener.notify(remoteEvent);
				} catch(UnknownEventException e) {
					e.printStackTrace();
				} catch(RemoteException e) {
					// Remove this listener from the list due to failure
					e.printStackTrace();
					//listenerList.remove(RemoteEventListener.class, listener);
					//System.out.println("notification failed, listener removed");
				}
			}
		}
	}

	@Override
	public void setProxy(Remote _proxy) throws RemoteException {
		// TODO Auto-generated method stub
		this.proxy = _proxy;
	}
	
	public void setAuctionTimeDuration(long timeInMicroSecond) {
	    Date timeToRun = new Date(System.currentTimeMillis() + timeInMicroSecond);
	    Timer timer = new Timer();
	    timer.schedule(new TimerTask() {
	            public void run() {
	                // Task here ...
	            	auctionStates.currentStatus = IAuction.AUCTION_FINISHED;
	            	fireNotify(EnglishAuctionImpl.D_AUCTION_TIMEOUT);
	            }
	        }, timeToRun);
	}
	
	public void setAuctionPrimitiveData(AuctionPrimitiveData _auctionPrimitiveData)
			throws RemoteException{
		this.auctionPrimitiveData = _auctionPrimitiveData;
		this.uniqueAuctionID = _auctionPrimitiveData.uniqueAuctionID;
	}

	@Override
	public String getUniqueAuctionID() throws RemoteException {
		// TODO Auto-generated method stub
		return this.uniqueAuctionID;
	}

	@Override
	public boolean paymentTransfer(float amount, String customerID,
			String providerID, TransactionManager mgr, long transactionID)
			throws RemoteException {
		// TODO Auto-generated method stub
		// TODO Auto-generated method stub
		try {
			//this object join the transaction
			mgr.join(transactionID, (TransactionParticipant) this.proxy, crashCount);
		} catch(UnknownTransactionException e) {
			e.printStackTrace();
		} catch(CannotJoinException e) {
			e.printStackTrace();
		} catch(CrashCountException e) {
			e.printStackTrace();
		} catch(RemoteException e) {
			e.printStackTrace();			
		}
		
		rma = null;
		rma = findAccountService();

		if(rma == null) {		//cannot find remote bank service
			return false;		//return false
		}
		
		try{	//if remote bank service found, transfer payment
			rma.creditDebit(amount, customerID, providerID, transactionID, mgr);
		} catch(RemoteException e){
			//Error occurs, return false
			e.printStackTrace();
			return false;
		}
		return true;
	}
	
	private IRemoteAccounts findAccountService(){
		ServiceDiscoveryManager clientMgr = null;
		try {
			LookupDiscoveryManager mgr = new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS,
																	null, // unicast locators
																	null); // DiscoveryListener
			clientMgr = new ServiceDiscoveryManager(mgr, new LeaseRenewalManager());
		} catch(Exception e) {
			e.printStackTrace();
			return null;
			//System.exit(1);
		}
		Class [] classes = new Class[] {IRemoteAccounts.class};
		ServiceTemplate template = new ServiceTemplate(null, classes,null);
		ServiceItem item = null;
		// Try to find the service, blocking until timeout if necessary
		try {
			item = clientMgr.lookup(template,
										null, // no filter
										DutchAuctionImpl.WAITFOR); // timeout
		} catch(Exception e) {
			e.printStackTrace();
			return null;
			//System.exit(1);
		}
		if (item == null) {
			// couldn't find a service in time
			System.out.println("No account service.");
			return null;
			//System.exit(1);
		}
		// Get the service
		IRemoteAccounts t_rma = (IRemoteAccounts) item.service;
		if (t_rma == null) {
			System.out.println("Transaction Manager is null.");
			return null;
			//System.exit(1);
		} 
		return t_rma;
	}

	@Override
	public void abort(TransactionManager arg0, long arg1)
			throws UnknownTransactionException, RemoteException {
		// TODO Auto-generated method stub
		//notify customer and provider that paymet cannot be done
		fireNotify(IAuction.CANNOT_TRANSFER_PAYMENT);
		//remove all current status of this auction
		auctionStates = new AuctionStates();
		System.out.println("Dutch transaction " + Long.toString(arg1) + " is aborting");
		
	}

	@Override
	public void commit(TransactionManager arg0, long arg1)
			throws UnknownTransactionException, RemoteException {
		// TODO Auto-generated method stub
		//notify customer and provider that payment is transfered successfully
		fireNotify(IAuction.PAYMENT_TRANSFERED_SUCCESSFULLY);
		System.out.println("Dutch transaction " + Long.toString(arg1) + " is committing");
		
	}

	@Override
	public int prepare(TransactionManager arg0, long arg1)
			throws UnknownTransactionException, RemoteException {
		// TODO Auto-generated method stub
		return TransactionConstants.PREPARED;
	}

	@Override
	public int prepareAndCommit(TransactionManager arg0, long arg1)
			throws UnknownTransactionException, RemoteException {
		// TODO Auto-generated method stub
		int result = prepare(arg0, arg1);
		if(result == TransactionConstants.PREPARED) {
			commit(arg0, arg1);
			result = TransactionConstants.COMMITTED;
		}
		return result;
	}
	
	public BidPriceData getLastBidPriceData() throws RemoteException {
		return this.lastBidPriceData;
	}
	
	public AuctionStates getCurrentAuctionStates() throws RemoteException {
		return this.auctionStates;
	}
	
	public CustomerData getLastJoinCustomer() throws RemoteException {
		return this.lastCustomerData;
	}

	@Override
	public AuctionPrimitiveData getAuctionPrimitiveData()
			throws RemoteException {
		// TODO Auto-generated method stub
		return this.auctionPrimitiveData;
	}
}
